package co.edu.uniandes.sd.mundo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.cert.X509Certificate;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.KeyAgreement;

import net.jxta.endpoint.Message;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.Message.ElementIterator;


public class ThreadManejoMensajes extends Thread{
	
	private Nodo nodo;
	
	private Message mensaje;
	
	public ThreadManejoMensajes(Nodo nodo,Message mensaje) {
		super();
		this.mensaje = mensaje;
		this.nodo = nodo;
	}


	public void run(){
		try{
			String cmd = mensaje.getMessageElement(Nodo.COMANDO).toString();
			if(cmd.equals(Nodo.AUTENTICACION)){
				InputStream certificado = mensaje.getMessageElement(Nodo.CERTIFICADO_DIGITAL).getStream();
				InputStream llave = mensaje.getMessageElement(Nodo.LLAVE).getStream();
				int num = Integer.parseInt(mensaje.getMessageElement(Nodo.NUM_CONSULTA).toString());
				String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
				nodo.manejarAutenticacion(certificado,llave,num,pipeInfo);
				
				
			}else if(cmd.equals(Nodo.SOLICITUD_AUTENTICACION)){
				int num = Integer.parseInt(mensaje.getMessageElement(Nodo.NUM_CONSULTA).toString());
				String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
				nodo.enviarAutenticacion(pipeInfo,num);
				
			}else if(cmd.equals(Nodo.MENSAJE)){ 
				byte[] tmp = parseStreamToBytes(mensaje.getMessageElement(Nodo.CERTIFICADO_DIGITAL).getStream());
				X509Certificate cert =  (X509Certificate)parseBytesToObject(tmp);
				cert.checkValidity();
				cert.verify(cert.getPublicKey());
				byte[] llaveP = parseStreamToBytes(mensaje.getMessageElement(Nodo.LLAVE).getStream());
				KeyAgreement ka = KeyAgreement.getInstance(Nodo.ALGORITMO_GENERACION_LLAVES);
				ka.init(nodo.getLlavePrivadaEncripcionSimetrica());
				X509EncodedKeySpec keySpec = new X509EncodedKeySpec(llaveP);
				KeyFactory keyFact = KeyFactory.getInstance(Nodo.ALGORITMO_GENERACION_LLAVES);
				ka.doPhase(keyFact.generatePublic(keySpec), true);
				Key llaveDH = ka.generateSecret(Nodo.ALGORITMO_SIMETRICO);
				tmp = parseStreamToBytes(mensaje.getMessageElement(Nodo.CONTENIDO).getStream());
				Message m =  desencriptarMensaje(tmp, llaveDH);
				
				byte[] mac1 = parseStreamToBytes(mensaje.getMessageElement(Nodo.MAC).getStream());
				keySpec = new X509EncodedKeySpec(llaveP);
				keyFact = KeyFactory.getInstance(Nodo.ALGORITMO_GENERACION_LLAVES);
				byte[] mac2 = nodo.generarMAC(tmp, keyFact.generatePublic(keySpec));
				if(!nodo.verificarMAC(mac1, mac2)){
					throw new Exception("Error verificando MAC");
				}
				
				mensaje = m;
				MessageElement el = mensaje.getMessageElement(Nodo.COMANDO);
				cmd = el.toString();
				System.out.println("##########################################");
				System.out.println("Recibi "+mensaje.toString());
				System.out.println("Comando:"+cmd);
				if(cmd.equals(Nodo.DESCARGAR_ARCHIVO)){
					String identificador = mensaje.getMessageElement(Nodo.CONTENIDO).toString();
					
					String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
					System.out.println("Descarga de "+identificador+" con "+pipeInfo);
					nodo.enviarArchivo(identificador, pipeInfo);
					
				}else if(cmd.equals(Nodo.ENVIAR_ARCHIVO)){
					InputStream is = mensaje.getMessageElement(Nodo.CONTENIDO).getStream();
					String identificador = mensaje.getMessageElement(Nodo.IDENTIFICADOR).toString();
					String palabrasClave = mensaje.getMessageElement(Nodo.PALABRAS_CLAVE).toString();
					String nombre = mensaje.getMessageElement(Nodo.NOMBRE).toString();
					InputStream is2 = mensaje.getMessageElement(Nodo.FIRMA_DIGITAL).getStream();
					String dueno = mensaje.getMessageElement(Nodo.DUENO_ARCHIVO).toString();
					nodo.almacenarArchivoDescargado(nombre,identificador,palabrasClave, is,is2,dueno);
				}else if(cmd.equals(Nodo.ARCHIVOS_CON_PALABRA_CLAVE)){
					String palabraClave = mensaje.getMessageElement(Nodo.CONTENIDO).toString();
					String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
					nodo.responderArchivosConPalabraClave(palabraClave, pipeInfo);
				}else if(cmd.equals(Nodo.RESPUESTA_PALABRA_CLAVE)){
					String archivos = mensaje.getMessageElement(Nodo.CONTENIDO).toString();
					nodo.agregarRespuestas(archivos);
					System.out.println(archivos);
				}else if(cmd.equals(Nodo.ARCHIVO_MAS_PARECIDO)){
					String contenido = mensaje.getMessageElement(Nodo.CONTENIDO).toString();
					String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
					long tamanio = Long.parseLong(contenido);
					nodo.responderArchivoMasParecido(tamanio, pipeInfo);
				}else if(cmd.equals(Nodo.RESPUESTA_ARCHIVO_MAS_PARECIDO)){
					String identificador = mensaje.getMessageElement(Nodo.CONTENIDO).toString();
					String tam = mensaje.getMessageElement(Nodo.TAMANIO).toString();
					long tamanio = Long.parseLong(tam);
					nodo.procesarRespuestaTamanio(identificador, tamanio);
				}else if(cmd.equals(Nodo.PEDIR_PERMISO)){
					String identificador = mensaje.getMessageElement(Nodo.CONTENIDO).toString();
					long timestamp = Long.parseLong(mensaje.getMessageElement(Nodo.TIMESTAMP).toString());
					String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
					nodo.solicitarPermiso(identificador, timestamp, pipeInfo);
				}else if(cmd.equals(Nodo.CONCEDER_PERMISO)){
					String identificador = mensaje.getMessageElement(Nodo.CONTENIDO).toString();
					String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
					nodo.registrarPermiso(identificador, pipeInfo);
				}else if(cmd.equals(Nodo.GUARDAR_ESTADO)){
					//ACA SE RECIBE LA PETICION PARA GUARDAR ESTADO - ME PIDE GUARDAR
					System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
					String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
					nodo.responderGuardarEstado(pipeInfo);
				}else if(cmd.equals(Nodo.RESPUESTA_GUARDAR_ESTADO)){
					//ACA SE RECIBE UNA RESPUESTA A UNA SOLICITUD DE GUARDAR ESTADO - ME RESPONDEN A UNA PETICION DE GUARDAR
					String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
					String idNodo = mensaje.getMessageElement(Nodo.NOMBRE).toString();
					InputStream is = mensaje.getMessageElement(Nodo.CONTENIDO).getStream();
					nodo.procesarRespuestaGuardarEstado(idNodo, pipeInfo, is);
				}else if(cmd.equals(Nodo.OBTENER_CERTIFICADO_DIGITAL)){
					String pipeInfo = mensaje.getMessageElement(Nodo.PIPE_INFO).toString();
					nodo.enviarCertificadoDigital(pipeInfo);
				}else if(cmd.equals(Nodo.CERTIFICADO_DIGITAL)){
					InputStream is = mensaje.getMessageElement(Nodo.CONTENIDO).getStream();
					nodo.verificarArchivo(is);
				}
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
	}

	private Message desencriptarMensaje(byte[] msj,Key llave) throws Exception{
		byte[] m = nodo.desencriptar(msj, llave);
		
		ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(m));
		Message msg = (Message)ois.readObject();
		ois.close();
		return msg;
	}
	
	private byte[] parseStreamToBytes(InputStream is)throws Exception{
		ByteArrayOutputStream bois = new ByteArrayOutputStream();
		int byt = is.read();
		while(byt >=0){
			bois.write(byt);
			byt = is.read();
		}
		bois.close();
		return bois.toByteArray(); 
		
	}
	
	private Object parseBytesToObject(byte[] bytes) throws Exception{
		ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
		Object o = ois.readObject();
		ois.close();
		return o;
		
	}
}
